import java.io.*;
import java.awt.*;

public class Bot
{
	private static int WEIGHT_MOVE = 1;
	private static int WEIGHT_ROTATION = 1;
	private static int WEIGHT_HOLE = 50;
	private static int WEIGHT_HEIGHT = 4;
	private static int WEIGHT_CLEAR1 = 0;
	private static int WEIGHT_CLEAR2 = -1;
	private static int WEIGHT_CLEAR3 = -2;
	private static int WEIGHT_CLEAR4 = -20;

	private static int SPEED = 250;
	private static int SPEED_DAMPEN = 20;
	private static int SPEED_DEFAULT_PULSES = 30;

	private GameLogic gameLogic = null;
	private EasySocket socket = null;

	private int id = -1;
	private String localName = "@Bot";
	private Player localPlayer = null;
	private int takeOverId = -1;

	private int lastPing = 0;
	private int lastAction = 0;
	private int actionSpeed = 250;
	private boolean gameStarted = false;

	private boolean powerupSlotsFilled[] = new boolean[3];

	private int actionDelay = 0; // Action delay is used to not have all bots perfectly synchronized

	public Bot(String gameServerAddress) {
		try {
			socket = new EasySocket(gameServerAddress, 5398);
		} catch (IOException e) {
		}
	}
	
	public Bot(String gameServerAddress, int takeOverId) {
		try {
			socket = new EasySocket(gameServerAddress, 5398);
		} catch (IOException e) {
		}

		this.takeOverId = takeOverId;
	}

	public String getName() {
		return localName;
	}

	public void setGameLogic(GameLogic gameLogic) {
		this.gameLogic = gameLogic;
		localPlayer = gameLogic.getPlayerByID(id);
		if (localPlayer == null && takeOverId == -1) {
			// If it should take over another player, then there will be a delay before the id exists
			sendAction("chat I can't find myself.");
			sendAction("disconnect");
		}
	}

	public void update(int delta) {
		while (socket.hasNextLine()) {
			String line = socket.nextLine();
			if (line.startsWith("id ")) {
				String[] params = line.split(" ", 2);
				id = Integer.parseInt(params[1]);
				sendAction("name "+localName+" "+id);
				//sendAction("chat I'm a bot with id "+id+"!");
				if (takeOverId != -1) {
					sendAction("cmd playeridswitch "+takeOverId);
					sendAction("playeridswitch "+takeOverId);
				}
			} else if (line.startsWith("gamestarted")) {
				gameStarted = true;
			} else if (line.startsWith("playeridswitch")) {
				String[] params = line.split(" ", 3);
				int replacementId = Integer.parseInt(params[1]);
				int oldId = Integer.parseInt(params[2]);
				if (oldId == id) {
					sendAction("chat I'm being replaced!");
					remove();
				} else if (replacementId == id) {
					gameStarted = true;
				}
			} else if (line.startsWith("rematch")) {
				gameStarted = false;
			}
		}
		if (localPlayer == null  && gameLogic != null) {
			System.out.println("Null player, trying to find it again.");
			localPlayer = gameLogic.getPlayerByID(id);
		}

		if (gameStarted && gameLogic != null && gameLogic.getPlayerByID(id) != null) {
			// Is a game active and we have the gamelogic state?
			if ((lastAction+=delta) > actionSpeed + actionDelay) {
				lastAction = 0;
				actionDelay = (int)(Math.random()*0.1*actionSpeed);
				actionSpeed = (int)(SPEED * (gameLogic.getPulsesPerDrop() + SPEED_DAMPEN) / (SPEED_DEFAULT_PULSES + SPEED_DAMPEN));

				String action = getAction();
				if (!action.equals("")) {
					sendAction(action);
				}
			}
		}

		if ((lastPing+=delta) > 2000) {
			lastPing = 0;
			sendAction("ping " + System.nanoTime());
		}
	}

	public int getId() {
		return id;
	}

	public void sendAction(String action) {
		socket.println(action);
	}

	public void remove() {
		sendAction("disconnect");
	}

	public void gotPowerup(int p) {
		if (p == Multitris.POWERUP_N || p == Multitris.POWERUP_G) {
			String powerString = (p == Multitris.POWERUP_N ? "nuke" : "gravity");
			double d = Math.random();
			String phrase;
			if(d < 0.1) {
				phrase = "Awesome";
			} else if (d < 0.2) {
				phrase = "Sweet";
			} else if (d < 0.3) {
				phrase = "Incredible";
			} else if (d < 0.4) {
				phrase = "Cool bananas";
			} else if (d < 0.5) {
				phrase = "Brilliant";
			} else if (d < 0.6) {
				phrase = "Yay, a";
			} else if (d < 0.7) {
				phrase = "About bloody time I got a";
			} else if (d < 0.8) {
				phrase = "I can has";
			} else if (d < 0.9) {
				phrase = "<3";
			} else {
				phrase = "I totally didn't see that";
			}
			if(d < 0.5) {
				sendAction("chat "+phrase+", I got a "+powerString+"!");
			} else {
				sendAction("chat "+phrase+" "+powerString+"");
			}
		}
		if (p == Multitris.POWERUP_S) {
			if(Math.random() < 0.3) {
				sendAction("I'll just take this split piece, then.");
			}
		}
	}


	// This is the gameplay part of the AI, the function that returns what action to do.
	public String getAction() {
		// Step 1: Find goal

		int hasPowerup = -1;
		int[] powerups = localPlayer.getPowerUps();
		for (int slot=0; slot<powerups.length; slot++) {
			if (powerups[slot] > 0) {
				hasPowerup = slot+1;
				if (powerupSlotsFilled[slot] == false) {
					powerupSlotsFilled[slot] = true;
					gotPowerup(powerups[slot]);
				}
			}
		}
		if (hasPowerup >= 0) {
			if (Math.random() < 0.01) {
				//sendAction("chat I'm using a powerup! You swing a bit less!");
				powerupSlotsFilled[hasPowerup-1] = false;
				return "cmd pwr" + hasPowerup;
			}
		}

		Board board = gameLogic.getBoard();
		Piece piece = localPlayer.getCurrentPiece();
		Point originalPoint = piece.getPoint().getLocation();
		int originalRotation = piece.getRotation();

		int lowestCost = Integer.MAX_VALUE;
		int lowestX = 0;
		int lowestRotation = 0;

		// For every rotation
		for (int nrRotations = 0; nrRotations < piece.getRotationStatesCount(); nrRotations++) {
			piece.rotate(1);
			// REWRITE THIS!

			int cwCost, ccwCost;
			if (piece.getRotation() < originalRotation) {
				cwCost = originalRotation - piece.getRotation();
				ccwCost = piece.getRotation() + (piece.getRotationStatesCount() - originalRotation);
			} else {
				ccwCost = piece.getRotation() - originalRotation;
				cwCost = originalRotation + (piece.getRotationStatesCount() - piece.getRotation());
			}

			int rotation = (cwCost < ccwCost ? cwCost : -ccwCost);
			int rotationCost = (int)Math.abs(rotation);

			// Check right
			for (int x=0; x<=10; x++) {
				piece.getPoint().x = originalPoint.x + x;
				// If it is already overlapping something else, then discard the position
				if (playerCollisionDetect(board, piece.getBricks(), piece.getPoint()) ||
					boundaryCollisionDetect(board, piece.getBricks(), piece.getPoint()) ||
					fixatedCollisionDetect(board, piece.getBricks(), piece.getPoint())) {
					break;
				}

				int cost = getCost(board, piece) + x * WEIGHT_MOVE + rotationCost * WEIGHT_ROTATION;

				if (cost < lowestCost) {
					lowestCost = cost;
					lowestX = x;
					lowestRotation = piece.getRotation();
				}
			}

			// Check left
			for (int x=-1; x>=-10; x--) {
				piece.getPoint().x = originalPoint.x + x;
				// If it is already overlapping something else, then discard the position
				if (playerCollisionDetect(board, piece.getBricks(), piece.getPoint()) ||
					boundaryCollisionDetect(board, piece.getBricks(), piece.getPoint()) ||
					fixatedCollisionDetect(board, piece.getBricks(), piece.getPoint())) {
					break;
				}

				int cost = getCost(board, piece) - x * WEIGHT_MOVE + rotationCost * WEIGHT_ROTATION;

				if (cost < lowestCost) {
					lowestCost = cost;
					lowestX = x;
					lowestRotation = piece.getRotation();
				}
			}
		}

		piece.setPoint(originalPoint);

		// Step 2: Execute an action towards the goal

		if (lowestCost < Integer.MAX_VALUE) { // A move was found
			System.out.println("COST: "+lowestCost);
			if (lowestRotation != piece.getRotation()) { // Should it be rotated?

				int cwCost, ccwCost;
				if (piece.getRotation() < lowestRotation) {
					cwCost = lowestRotation - piece.getRotation();
					ccwCost = piece.getRotation() + (piece.getRotationStatesCount() - lowestRotation);
				} else {
					ccwCost = piece.getRotation() - lowestRotation;
					cwCost = lowestRotation + (piece.getRotationStatesCount() - piece.getRotation());
				}

				if (cwCost < ccwCost) {
					return "cmd _cw";
				} else {
					return "cmd ccw";
				}
			} else if (lowestX != 0) { // Should it be moved?
				if (lowestX < 0) {
					return "cmd lft";
				} else {
					return "cmd rgt";
				}
			} else {
				if (lowestCost < 50) {
					return "cmd dwn";
				}
			}
		}

		return "";
	}

	// Calculate the cost for placing the piece here
	public int getCost(Board board, Piece piece) {
		Brick[][] matrix = piece.getBricks();
		Point point = piece.getPoint().getLocation();

		// Move the piece down one step at a time until it collides with something.
		while (true) {
			// If it collides with a player piece, then discard this position
			if (playerCollisionDetect(board, matrix, point)) {
				return 1000000000;
			}
			// If it collides with the ground, try to calculate how good the placement is
			if (boundaryCollisionDetect(board, matrix, point) ||
				fixatedCollisionDetect(board, matrix, point)) {
				point.y++; // Undo move

				// Count "holes"
				int holes = 0;
				for (int x=-1; x<matrix.length+1; x++) {
					boolean countHoles = false;
					for (int y=matrix[0].length; y + point.y >= 0; y--) {
						boolean brick = false;
						if (x + point.x >= 0 && x + point.x < board.getColumns() &&
							y + point.y >= 0 && y + point.y < board.getRows()) { // Inside the board
							if (board.getBrick(x + point.x, y + point.y) != null) {
								countHoles = true;
								brick = true;
							}
						}
						/*if (x >= 0 && x < matrix.length && y >= 0 && y < matrix[0].length) {
							if (matrix[x][y] != null) {
								countHoles = true;
								brick = true;
							}
						}*/

						if (brick == false && countHoles) {
							holes--;
						}
					}
				}
				for (int x=-1; x<matrix.length+1; x++) {
					boolean countHoles = false;
					for (int y=matrix[0].length; y + point.y >= 0; y--) {
						boolean brick = false;
						if (x + point.x >= 0 && x + point.x < board.getColumns() &&
							y + point.y >= 0 && y + point.y < board.getRows()) { // Inside the board
							if (board.getBrick(x + point.x, y + point.y) != null) {
								countHoles = true;
								brick = true;
							}
						}
						if (x >= 0 && x < matrix.length && y >= 0 && y < matrix[0].length) {
							if (matrix[x][y] != null) {
								countHoles = true;
								brick = true;
							}
						}

						if (brick == false && countHoles) {
							holes++;
						}
					}
				}

				int rows = 0;
				// Count how many rows that would be cleared
				for(int y=board.getRows()-1; y>=0; y--) {
					Brick[] row = board.getRow(y);
					boolean fullRow = true;
					for(int x=0; x<row.length; x++) {
						if (row[x] != null) {
						} else if (x-point.x >= 0 && x-point.x < matrix.length &&
									y-point.y >= 0 && y-point.y < matrix[0].length &&
									matrix[x-point.x][y-point.y] != null){
						} else {
							fullRow = false;
							break;
						}
					}
					if (fullRow) {
						rows++;
					}
				}
				int rowCost = 0;
				switch (rows) {
					case 1: rowCost = WEIGHT_CLEAR1; break;
					case 2: rowCost = WEIGHT_CLEAR2; break;
					case 3: rowCost = WEIGHT_CLEAR3; break;
					case 4: rowCost = WEIGHT_CLEAR4; break;
				}

				return holes * WEIGHT_HOLE + point.y * WEIGHT_HEIGHT + rowCost;
			}

			point.y--;
		}
	}

    private boolean fixatedCollisionDetect(Board board, Brick[][] matrix, Point point)
    {
		int xcord;
		int ycord;
		for(int x=0; x<4; x++) {
			for(int y=0; y<4; y++) {
			//if there is a brick in this position
			if(matrix[x][y] != null) {
				xcord = point.x + x;
				ycord = point.y + y;
				//if the board have a brick in the xcord,ycord we have a collision
				if (xcord >= 0 && xcord < board.getColumns() && ycord >= 0 && ycord < board.getRows()) {
					if(board.getBrick(xcord,ycord) != null) return true;
				}
			}
			}
		}
		return false;
    }

	private boolean boundaryCollisionDetect(Board board, Brick[][] matrix, Point point)
    {
		int xcord;
		int ycord;
		for(int x=0; x<4; x++) {
			for(int y=0; y<4; y++) {
			//if there is a brick in this position
			if(matrix[x][y] != null) {
				xcord = point.x + x;
				ycord = point.y + y;
				//if the brick is outside the boundaries
				if(xcord>=board.getColumns() || xcord<0 || ycord<0) return true;

			}
			}
		}
		return false;
    }

	private boolean playerCollisionDetect(Board board, Brick[][] matrix, Point point)
	{
		//the x and y coordinate of one brick
		int xcord;
		int ycord;
		for(int x=0; x<4; x++) {
			for(int y=0; y<4; y++) {
				//if there is a brick in this position
				if(matrix[x][y] != null) {
					xcord = point.x + x;
					ycord = point.y + y;
					//if the brick intersects another player's brick
					if(isPlayerColliding(xcord,ycord,localPlayer)) return true;

				}
			}
		}
		return false;
	}

	private boolean isPlayerColliding(int xcord, int ycord, Player player)
	{
		for (Player otherPlayer : gameLogic.getPlayers()) {
//		for(int i=0; i<players.size(); i++) {
//			Player otherPlayer = players.get(i);
			//if the players are not the same player.. eeh...
			if(player != otherPlayer) {
				//the coordinate of some player's piece
				Point point = otherPlayer.getCurrentPiece().getPoint();
				//the coordinates of some player's brick
				int xcord2;
				int ycord2;
				//the brick array of some other player
				Brick[][] matrix = otherPlayer.getCurrentPiece().getBricks();
				if (xcord - point.x < 0 || xcord - point.x >= matrix.length) {
				} else if (ycord - point.y < 0 || ycord - point.y >= matrix[0].length) {
				} else if (matrix[xcord - point.x][ycord - point.y] != null) {
					return true;
				}
			}
		}
		return false;
	}
}